package de.fanuffel.generic;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import de.fanuffel.generic.functions.Complement;
import de.fanuffel.generic.functions.Function;
import de.fanuffel.generic.functions.Predicate;
import de.fanuffel.generic.structures.Pair;

public final class HOF {

  public static <A, B> B mapReduce(Collection<A> as, Function<A, B> map,
      Function<Pair<B, B>, B> reduce, B v0) {
    return fold(mapVector(as, map), reduce, v0);
  }

  public static final <Dom> Function<Pair<Pair<Collection<Dom>, Function<Pair<Dom, Dom>, Dom>>, Dom>, Dom> FOLD() {
    return new Function<Pair<Pair<Collection<Dom>, Function<Pair<Dom, Dom>, Dom>>, Dom>, Dom>() {
      public Dom eval(
          Pair<Pair<Collection<Dom>, Function<Pair<Dom, Dom>, Dom>>, Dom> arg) {
        Collection<Dom> c = arg.getFirst().getFirst();
        Function<Pair<Dom, Dom>, Dom> f = arg.getFirst().getSecond();
        Dom v = arg.getSecond();
        return HOF.fold(c, f, v);
      }
    };
  }

  @SuppressWarnings("unchecked") public static <A, B> Function<A, B> reifyStatic(
      final Class<?> clss, final String s, final Class<?>... arg) {
    try {
      final Method m = clss.getDeclaredMethod(s, arg);
      return (Function<A, B>) new Function<Object, Object>() {
        public Object eval(Object arg) {
          try {
            return m.invoke(null, arg);
          } catch (IllegalArgumentException error) {
            throw new RuntimeException("Cannot apply reified function "
                + clss.toString() + "." + s, error);
          } catch (IllegalAccessException error) {
            throw new RuntimeException("Cannot apply reified function "
                + clss.toString() + "." + s, error);
          } catch (InvocationTargetException error) {
            throw new RuntimeException("Cannot apply reified function "
                + clss.toString() + "." + s, error);
          }
        }
      };

    } catch (SecurityException error1) {
      throw new RuntimeException("Cannot reify function " + clss.toString()
          + "." + s, error1);
    } catch (NoSuchMethodException error1) {
      throw new RuntimeException("Cannot reify function " + clss.toString()
          + "." + s, error1);
    }

  }

  public static <A, B> Function<A, B> safeReify(final Object self,
      Class<? extends A> argType, Class<? super B> retType) {
    return null;
  }

  @SuppressWarnings("unchecked") public static <A, B> Function<A, B> reify(
      final Object self, final String s, final Class<?>... arg) {
    final Class<?> c = self.getClass();
    try {
      final Method m = c.getDeclaredMethod(s, arg);
      return (Function<A, B>) new Function<Object, Object>() {
        public Object eval(Object arg) {
          try {
            return m.invoke(self, arg);
          } catch (IllegalArgumentException error) {
            throw new RuntimeException("Cannot apply reified function "
                + c.toString() + "." + s, error);
          } catch (IllegalAccessException error) {
            throw new RuntimeException("Cannot apply reified function "
                + c.toString() + "." + s, error);
          } catch (InvocationTargetException error) {
            throw new RuntimeException("Cannot apply reified function "
                + c.toString() + "." + s, error);
          }
        }
      };
    } catch (SecurityException error1) {
      throw new RuntimeException("Cannot reify function " + c.toString() + "."
          + s, error1);
    } catch (NoSuchMethodException error1) {
      throw new RuntimeException("Cannot reify function " + c.toString() + "."
          + s, error1);
    }

  }

  public static <A, B> B fold(Collection<A> as, Function<Pair<A, B>, B> f, B v0) {
    B v = v0;
    for (A a : as) {
      v = f.eval(new Pair<A, B>(a, v));
    }
    return v;
  }

  @SuppressWarnings("unchecked") public static <Dom, Cod> Vector<Cod> mapVector(
      Iterable<Dom> c, Function<Dom, Cod> f) {
    return mapAdd(c, f, (Vector<Cod>) new Vector());
  }

  public static <S, T, E extends Collection<T>> E mapAdd(Iterable<S> source,
      Function<S, T> f, final E target) {

    for (S element : source) {
      target.add(f.eval(element));
    }

    return target;

  }

  public static <Dom> boolean exists(Collection<Dom> c, Predicate<Dom> p) {
    for (Dom d : c) {
      if (p.in(d))
        return true;
    }
    return false;
  }

  public static <Dom> boolean forall(Collection<Dom> c, Predicate<Dom> p) {
    return !exists(c, new Complement<Dom>(p));
  }

  public static <Dom> Dom first(Collection<Dom> c, Predicate<Dom> p) {
    for (Iterator<Dom> iter = c.iterator(); iter.hasNext();) {
      Dom el = iter.next();
      if (p.in(el))
        return el;
    }
    throw new RuntimeException("No such element");
  }
}
